package cn.fufeiii.bt.common.util;

import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import java.io.ByteArrayOutputStream;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

/**
 * RSA 加/解密 加/验签
 *
 * @author FuFei
 */
public final class RsaUtil {

    /**
     * 进行RSA进行签名
     *
     * @param content    内容字节数组
     * @param privateKey 私钥字节数组
     * @return 签名字节数组
     */
    public static byte[] signature(byte[] content, byte[] privateKey) {
        try {
            Signature signer = Signature.getInstance("SHA256withRSA");
            signer.initSign(KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKey)));
            signer.update(content);
            return signer.sign();
        } catch (InvalidKeySpecException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 进行RSA进行签名
     */
    public static byte[] signature(String content, String privateKeyBase64) {
        return signature(content.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(privateKeyBase64));
    }


    /**
     * 进行RSA进行验签
     *
     * @param content   内容字节
     * @param signature 签名字节
     * @param publicKey 公钥字节
     * @return 验签结果
     */
    public static boolean verify(byte[] content, byte[] signature, byte[] publicKey) {
        try {
            Signature verifier = Signature.getInstance("SHA256withRSA");
            verifier.initVerify(KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKey)));
            verifier.update(content);
            return verifier.verify(signature);
        } catch (InvalidKeySpecException | NoSuchAlgorithmException | SignatureException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 进行RSA进行验签
     */
    public static boolean verify(String content, String signatureBase64, String publicKeyBase64) {
        return verify(content.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(signatureBase64), Base64.getDecoder().decode(publicKeyBase64));
    }


    public static byte[] encrypt(byte[] content, byte[] privateKey, int keySize) {
        try {
            Cipher encryptCipher = Cipher.getInstance("RSA");
            encryptCipher.init(Cipher.ENCRYPT_MODE, KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKey)));
            return codec(encryptCipher, Cipher.ENCRYPT_MODE, content, keySize);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] encrypt(String content, String privateKeyBase64, int keySize) {
        return encrypt(content.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(privateKeyBase64), keySize);
    }

    public static byte[] encrypt(String content, String privateKeyBase64) {
        return encrypt(content.getBytes(StandardCharsets.UTF_8), Base64.getDecoder().decode(privateKeyBase64), 1024);
    }


    public static byte[] decrypt(byte[] encryptContent, byte[] publicKey, int keySize) {
        try {
            Cipher decryptCipher = Cipher.getInstance("RSA");
            decryptCipher.init(Cipher.DECRYPT_MODE, KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(publicKey)));
            return codec(decryptCipher, Cipher.DECRYPT_MODE, encryptContent, keySize);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeySpecException | InvalidKeyException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] decrypt(String encryptContentBase64, String publicKey, int keySize) {
        return decrypt(Base64.getDecoder().decode(encryptContentBase64), Base64.getDecoder().decode(publicKey), keySize);
    }

    public static byte[] decrypt(String encryptContentBase64, String publicKey) {
        return decrypt(Base64.getDecoder().decode(encryptContentBase64), Base64.getDecoder().decode(publicKey), 1024);
    }


    /**
     * 分段加解密
     * 当待加密内容长度大于密钥长度字节时，需要分段进行加解密
     * <a href="https://blog.csdn.net/u012272367/article/details/106668897"/>
     *
     * @param cipher   cipher
     * @param mode     模式
     * @param dataByte 被加密数据字节
     * @param keySize  密钥长度
     */
    private static byte[] codec(Cipher cipher, int mode, byte[] dataByte, int keySize) {
        // 最大块
        int maxBlock;
        if (mode == Cipher.DECRYPT_MODE) {
            maxBlock = keySize / 8;
        } else {
            maxBlock = keySize / 8 - 11;
        }
        int offset = 0;
        byte[] buff;
        int i = 0;
        try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            while (dataByte.length > offset) {
                if (dataByte.length - offset > maxBlock) {
                    // 可以调用以下的doFinal（）方法完成加密或解密数据：
                    buff = cipher.doFinal(dataByte, offset, maxBlock);
                } else {
                    buff = cipher.doFinal(dataByte, offset, dataByte.length - offset);
                }
                out.write(buff, 0, buff.length);
                i++;
                offset = i * maxBlock;
            }
            return out.toByteArray();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

}
